#include "lua.h"
#include "lauxlib.h"
#include "stdio.h"
#include "quickjs.h"
#include "quickjs-libc.h"
#include "string.h"

#if defined(__APPLE__)
#include <malloc/malloc.h>
#elif defined(__linux__)
#include <malloc.h>
#endif

#include "cutils.h"

#if defined(_WIN32)
#include <windows.h>
#include <shlobj.h>
#include <winerror.h>
#include <winsock2.h>

static SOCKET s;
static struct sockaddr_in server;

typedef int(WSAAPI *LPFN_WSASTARTUP)(WORD, LPWSADATA);
typedef int(WSAAPI *LPFN_WSACLEANUP)(void);
typedef SOCKET(WSAAPI *LPFN_SOCKET)(int, int, int);
typedef int(WSAAPI *LPFN_SENDTO)(SOCKET, const char *, int, int, const struct sockaddr *, int);
typedef int(WSAAPI *LPFN_CLOSESOCKET)(SOCKET);
typedef unsigned long(WSAAPI *LPFN_INET_ADDR)(const char *);

static LPFN_SENDTO sendto2;

void send_udp_message(const char *message)
{
    // 发送消息
    if (sendto2(s, message, strlen(message), 0, (struct sockaddr *)&server, sizeof(server)) == SOCKET_ERROR)
    {
        // printf("发送消息失败. 错误代码: %d\n", WSAGetLastError());
    }
    else
    {
        // printf("消息发送成功: %s\n", message);
    }
}

#endif

static JSRuntime *rt;
static JSContext *ctx;

static int eval_buf(JSContext *ctx, const void *buf, int buf_len,
                    const char *filename, int eval_flags)
{
    JSValue val;
    int ret;

    if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE)
    {
        /* for the modules, we compile then run to be able to set
           import.meta */
        val = JS_Eval(ctx, buf, buf_len, filename,
                      eval_flags | JS_EVAL_FLAG_COMPILE_ONLY);
        if (!JS_IsException(val))
        {
            js_module_set_import_meta(ctx, val, TRUE, TRUE);
            val = JS_EvalFunction(ctx, val);
        }
    }
    else
    {
        val = JS_Eval(ctx, buf, buf_len, filename, eval_flags);
    }
    if (JS_IsException(val))
    {
        js_std_dump_error(ctx);
        ret = -1;
    }
    else
    {
        ret = 0;
    }
    JS_FreeValue(ctx, val);
    return ret;
}

extern int has_suffix(const char *str, const char *suffix);
extern JSModuleDef *js_init_module_socket(JSContext *ctx, const char *module_name);

static int eval_file(JSContext *ctx, const char *filename, int module)
{
    uint8_t *buf;
    int ret, eval_flags;
    size_t buf_len;

    buf = js_load_file(ctx, &buf_len, filename);
    if (!buf)
    {
        perror(filename);
        return -1;
        // exit(1);
    }

    if (module < 0)
    {
        module = (has_suffix(filename, ".mjs") ||
                  JS_DetectModule((const char *)buf, buf_len));
    }
    if (module)
        eval_flags = JS_EVAL_TYPE_MODULE;
    else
        eval_flags = JS_EVAL_TYPE_GLOBAL;
    ret = eval_buf(ctx, buf, buf_len, filename, eval_flags);
    js_free(ctx, buf);
    return ret;
}

// #include <lualib.h>
static JSContext *JS_NewCustomContext(JSRuntime *rt)
{
    JSContext *ctx;
    ctx = JS_NewContext(rt);
    if (!ctx)
        return NULL;
#ifdef CONFIG_BIGNUM
    if (bignum_ext)
    {
        JS_AddIntrinsicBigFloat(ctx);
        JS_AddIntrinsicBigDecimal(ctx);
        JS_AddIntrinsicOperators(ctx);
        JS_EnableBignumExt(ctx, TRUE);
    }
#endif
    /* system modules */
    js_init_module_std(ctx, "std");
    js_init_module_os(ctx, "os");
    js_init_module_socket(ctx, "socket");
    return ctx;
}

static int string_includes(char *str, char *search)
{
    JSValue g = JS_GetGlobalObject(ctx);
    JSValue string_includes = JS_GetPropertyStr(ctx, g, "_string_includes");
    JSValue jsstr = JS_NewString(ctx, str);
    JSValue jssearch = JS_NewString(ctx, search);
    JSValue arr[2] = {jsstr, jssearch};
    JSValue res = JS_Call(ctx, string_includes, JS_UNDEFINED, 2, arr);

    int resutl = JS_ToBool(ctx, res);

    JS_FreeValue(ctx, string_includes);
    JS_FreeValue(ctx, jsstr);
    JS_FreeValue(ctx, jssearch);
    JS_FreeValue(ctx, res);
    JS_FreeValue(ctx, g);
    return resutl;
}

static char *string_replace(char *str, char *search, char *replace)
{
    JSValue g = JS_GetGlobalObject(ctx);
    JSValue string_replace = JS_GetPropertyStr(ctx, g, "_string_replace");
    JSValue jsstr = JS_NewString(ctx, str);
    JSValue jssearch = JS_NewString(ctx, search);
    JSValue jsreplace = JS_NewString(ctx, replace);
    JSValue arr[3] = {jsstr, jssearch, jsreplace};
    JSValue res = JS_Call(ctx, string_replace, JS_UNDEFINED, 3, arr);

    char *resutl = JS_ToCString(ctx, res);

    JS_FreeValue(ctx, string_replace);
    JS_FreeValue(ctx, jsstr);
    JS_FreeValue(ctx, jssearch);
    JS_FreeValue(ctx, jsreplace);
    JS_FreeValue(ctx, res);
    JS_FreeValue(ctx, g);
    return resutl;
}

int jhqjs_load(lua_State *L)
{
    char *filename = lua_tostring(L, 1);
    for (size_t i = 0; i < strlen(filename); i++)
    {
        if (filename[i] == '\\')
        {
            filename[i] = '/';
        }
    }
    int needfreefilename = 0;
    FILE *file = fopen(filename, "r");
    if (file == NULL)
    {
        if (string_includes(filename, "../mods/workshop-"))
        {
            char name[2048] = {0};
            int end = strlen(filename) - 17;
            strncpy(name, filename + 17, end);
            name[end] = '\0';

            char newPath[2048] = {0};
            strcat(newPath, "../../../workshop/content/322330/");
            strcat(newPath, name);
            filename = newPath; // string_replace(filename, "../mods/workshop-", "../../../workshop/content/");
        }

        file = fopen(filename, "r");
        if (file == NULL)
        {
            char err[2048] = {0};
            strcat(err, "文件未找到: ");
            strcat(err, filename);
            lua_pushstring(L, err);
            if (needfreefilename)
                JS_FreeCString(ctx, filename);
            return lua_error(L);
        }
        fclose(file);
    }
    fclose(file);

    if (eval_file(ctx, filename, 1))
    {
        char err[2048] = {0};
        strcat(err, filename);
        strcat(err, " 执行出错!!!\n");
        printf("%s", err);
        lua_pushstring(L, err);
        if (needfreefilename)
            JS_FreeCString(ctx, filename);
        return lua_error(L);
    }
    printf("%s 执行完毕，进入事件循环\n", filename);
    if (needfreefilename)
        JS_FreeCString(ctx, filename);
    return 0;
}

extern int (*os_poll_func)(JSContext *ctx);
extern int hloop_run_while_native();
int jhqjs_loop(lua_State *L)
{
    hloop_run_while_native();
    JSContext *ctx1;
    int err;

    /* execute the pending jobs */
    for (;;)
    {
        err = JS_ExecutePendingJob(rt, &ctx1);
        if (err <= 0)
        {
            if (err < 0)
            {
                js_std_dump_error(ctx);
            }
            break;
        }
    }

    JSValue g = JS_GetGlobalObject(ctx);
    JSValue gameFrameCallback = JS_GetPropertyStr(ctx, g, "__gameFrameCallback");
    if (!JS_IsUndefined(gameFrameCallback) && JS_IsFunction(ctx, gameFrameCallback))
    {
        JSValue frame = JS_Call(ctx, gameFrameCallback, JS_UNDEFINED, 0, NULL);
        int res;
        JS_ToInt32(ctx, &res, frame);
        char buf[32] = {0};
        sprintf(buf, "%s%d", "gameFrameCallback:", res);
        // send_udp_message(buf);
        JS_FreeValue(ctx, frame);
    }
    JS_FreeValue(ctx, gameFrameCallback);
    JS_FreeValue(ctx, g);
    if (!os_poll_func || os_poll_func(ctx))
    {
        return 0;
    }
    return 0;
}

typedef int BOOL;
extern struct trace_malloc_data;
static int isInit = 0;
int jhqjs_init(lua_State *L)
{
    if (!isInit)
    {
        isInit = 1;
    }
    else
    {
        printf("实例已经初始化！！！，无需重新init");
        // lua_pushstring(L, "实例已经初始化！！！，无需重新init");
        // lua_error(L);
        return 0;
    }
#if defined(_WIN32)

    HMODULE hWs2_32 = LoadLibrary("ws2_32.dll");
    if (hWs2_32 == NULL)
    {
        lua_pushstring(L, "加载ws2_32.dll失败");
        return 0;
    }

    LPFN_WSASTARTUP WSAStartup = (LPFN_WSASTARTUP)GetProcAddress(hWs2_32, "WSAStartup");
    LPFN_WSACLEANUP WSACleanup = (LPFN_WSACLEANUP)GetProcAddress(hWs2_32, "WSACleanup");
    LPFN_SOCKET socket = (LPFN_SOCKET)GetProcAddress(hWs2_32, "socket");
    LPFN_CLOSESOCKET closesocket = (LPFN_CLOSESOCKET)GetProcAddress(hWs2_32, "closesocket");
    LPFN_INET_ADDR inet_addr = (LPFN_INET_ADDR)GetProcAddress(hWs2_32, "inet_addr");
    u_short (*htons)(u_short) = (void *)GetProcAddress(hWs2_32, "htons");

    sendto2 = (LPFN_SENDTO)GetProcAddress(hWs2_32, "sendto");

    if (!WSAStartup || !WSACleanup || !socket || !sendto || !closesocket || !inet_addr)
    {
        FreeLibrary(hWs2_32);
        lua_pushstring(L, "获取函数地址失败");
        return 0;
    }

    WSADATA wsa;

    // 初始化Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    {
        // printf("Winsock初始化失败. 错误代码: %d\n", WSAGetLastError());
        FreeLibrary(hWs2_32);
        lua_pushstring(L, "Winsock初始化失败");
        return 0;
    }

    // 创建套接字
    if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
    {
        // printf("创建套接字失败. 错误代码: %d\n", WSAGetLastError());
        WSACleanup();
        FreeLibrary(hWs2_32);
        lua_pushstring(L, "创建套接字失败");
        return 0;
    }

    // 设置服务器地址
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr("127.0.0.1");
    server.sin_port = htons(64586);

    // // 关闭套接字
    // closesocket(s);
    // WSACleanup();
    // FreeLibrary(hWs2_32);

    const char *message = "Hello, UDP!";

    send_udp_message(message);

end:;

#endif

    // 从 L 读取一个布尔值
    int outToFile = lua_toboolean(L, 1);

    if (outToFile)
    {
#if defined(__APPLE__)
        const char *homeDir = getenv("HOME");
        const char logPath[1024] = {};
        const char errPath[1024] = {0};
        if (homeDir != NULL)
        {
            sprintf(logPath, "%s/Documents/Klei/DoNotStarveTogether/client_qjs_log.txt", homeDir);
            sprintf(errPath, "%s/Documents/Klei/DoNotStarveTogether/client_qjs_err.txt", homeDir);
        }
        else
        {
            sprintf(logPath, "~/Documents/Klei/DoNotStarveTogether/client_qjs_log.txt", homeDir);
        }
#endif
#if defined(_WIN32)
        const char logPath[1024] = {0};
        const char errPath[1024] = {0};
        if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, logPath)))
        {
            strcat(errPath, logPath);
            strcat(logPath, "\\Klei\\DoNotStarveTogether\\client_qjs_log.txt");
            strcat(errPath, "\\Klei\\DoNotStarveTogether\\client_qjs_err.txt");
        }
        else
        {

            printf("Failed to get Documents folder path.\n");
        }
#endif
        FILE *logfile = freopen(logPath, "w", stdout);
        if (logfile == NULL)
        {
            printf("无法打开日志文件\n");
        }
        printf("日志文件：%s\n", logPath);
        FILE *errfile = freopen(errPath, "w", stderr);
        if (errfile == NULL)
        {
            printf("无法打开错误文件\n");
        }
        printf("错误文件：%s\n", errPath);
    }

    int argc = 2;
    int optind;
    char *expr = NULL;
    int interactive = 0;
    int dump_memory = 0;
    int trace_memory = 0;
    int empty_run = 0;
    int module = 1;
    int load_std = 1;
    int dump_unhandled_promise_rejection = 1;
    size_t memory_limit = 0;
    char *include_list[32];
    int i, include_count = 0;
#ifdef CONFIG_BIGNUM
    int load_jscalc;
#endif
    size_t stack_size = 0;

    /* cannot use getopt because we want to pass the command line to
       the script */
    optind = 1;

#ifdef CONFIG_BIGNUM
    if (load_jscalc)
        bignum_ext = 1;
#endif

    rt = JS_NewRuntime();
    if (!rt)
    {
        fprintf(stderr, "qjs: cannot allocate JS runtime\n");
        exit(2);
    }
    if (memory_limit != 0)
        JS_SetMemoryLimit(rt, memory_limit);
    if (stack_size != 0)
        JS_SetMaxStackSize(rt, stack_size);
    js_std_set_worker_new_context_func(JS_NewCustomContext);
    js_std_init_handlers(rt);
    ctx = JS_NewCustomContext(rt);
    if (!ctx)
    {
        fprintf(stderr, "qjs: cannot allocate JS context\n");
        exit(2);
    }

    /* loader for ES6 modules */
    JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);

    if (dump_unhandled_promise_rejection)
    {
        JS_SetHostPromiseRejectionTracker(rt, js_std_promise_rejection_tracker,
                                          NULL);
    }

    if (!empty_run)
    {
#ifdef CONFIG_BIGNUM
        if (load_jscalc)
        {
            js_std_eval_binary(ctx, qjsc_qjscalc, qjsc_qjscalc_size, 0);
        }
#endif
        char *temp = "";
        js_std_add_helpers(ctx, 1, &temp);

        /* make 'std' and 'os' visible to non module code */
        if (load_std)
        {
#include "loop_event.h"
            eval_buf(ctx, loop_event_script, strlen(loop_event_script), "<input>", JS_EVAL_TYPE_MODULE);
        }

        // char *filename = "/Users/zhang/Library/Application Support/Steam/steamapps/common/Don't Starve Together/dontstarve_steam.app/Contents/mods/qjs/js/main.mjs";
        // if (eval_file(ctx, filename, 1))
        // {
        //     goto fail;
        // }
    }

    if (dump_memory)
    {
        JSMemoryUsage stats;
        JS_ComputeMemoryUsage(rt, &stats);
        JS_DumpMemoryUsage(stdout, &stats, rt);
    }
    printf("JHQJS 初始化完成\n");
    return 0;
fail:
    js_std_free_handlers(rt);
    JS_FreeContext(ctx);
    JS_FreeRuntime(rt);
    exit(1);

    return 0;
}

int jhqjs_version(lua_State *L)
{
    lua_pushnumber(L, 1);
    return 1;
}

int jhqjs_test(lua_State *L)
{
    return 0;
}

// static const struct luaL_Reg jhqjs[] = {
//     {"init", init},
//     {"load", load},
//     {"loop", loop},
//     {"test", test},
//     {NULL, NULL}};
static int version(lua_State *L)
{
    lua_pushinteger(L, 1);
    return 1;
}

// int luaopen_jhqjs(lua_State *L)
// {
//     luaL_register(L, "jhqjs", jhqjs);
//     return 1;
// }
#ifdef __APPLE__
extern void inject_entry_jhqjs();
__attribute__((constructor)) void inject_entry_init_jhqjs()
{
    inject_entry_jhqjs();
}
#endif

#ifdef _WIN32
extern BOOL inject_entry_jhqjs(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved);
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    return inject_entry_jhqjs(hModule, ul_reason_for_call, lpReserved);
}
#endif